﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Tools.Ribbon;
using Microsoft.Win32;
using Microsoft.Office.Interop.Excel;
using System.Data;
using System.Windows.Forms;
using AcceptanceTest.Framework;
using System.Deployment.Application;
using System.Reflection;
using System.IO;

namespace AcceptanceTestExcelAddIn
{
    public partial class Ribbon1 : OfficeRibbon
    {
        private Dictionary<string, Type> tests;

        public Ribbon1()
        {
            InitializeComponent();
        }

        private void Ribbon1_Load(object sender, RibbonUIEventArgs e)
        {
            if (ApplicationDeployment.IsNetworkDeployed)
            {
                this.group1.Label = string.Format("Acceptance Test {0}",
                    ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString());
            }

            LoadTestLibraryAuto();
            AddTestLibraryToUI(tests);
        }

        #region utils
        public static string GetCellValue(int cellRow, int cellColumn, Worksheet worksheet)
        {
            object val = ((Range)worksheet.Cells[cellRow, cellColumn]).get_Value(Type.Missing);
            return val == null ? "" : val.ToString().Trim();
        }

        public static string GetCellValue(int cellRow, int cellColumn)
        {
            Worksheet worksheet = Globals.ThisAddIn.Application.ActiveWorkbook.ActiveSheet as Worksheet;
            return GetCellValue(cellRow, cellColumn, worksheet);
        }

        public static string GetCellValue(int cellRow, int cellColumn, int sheetNumber)
        {
            Worksheet worksheet = Globals.ThisAddIn.Application.ActiveWorkbook.Sheets[sheetNumber] as Worksheet;
            return GetCellValue(cellRow, cellColumn, sheetNumber);
        }

        public static void SetCellValue(int cellRow, int CellColumn, string data, Worksheet worksheet)
        {
            worksheet.Cells[cellRow, CellColumn] = data;
        }

        public static void SetCellValue(int cellRow, int cellColumn, string data)
        {
            Worksheet worksheet = Globals.ThisAddIn.Application.ActiveWorkbook.ActiveSheet as Worksheet;
            SetCellValue(cellRow, cellColumn, data, worksheet);
        }

        public static void SetCellValue(int cellRow, int cellColumn, string data, int sheetNumber)
        {
            Worksheet worksheet = Globals.ThisAddIn.Application.ActiveWorkbook.Sheets[sheetNumber] as Worksheet;
            SetCellValue(cellRow, cellColumn, data, worksheet);
        }


        public static void SetColor(Range range, int colorIdx)
        {
            range.Interior.ColorIndex = colorIdx;
        }

        public static System.Data.DataTable GetDataTableFromRange(Range range, string name, Dictionary<string, Range> rangeCache)
        {
            var dt = new System.Data.DataTable(name);

            Range rg = range.get_Resize(1, 1);
            for (int c = 0; c < range.Columns.Count; c++)
            {
                dt.Columns.Add(new DataColumn
                {
                    ColumnName = rg.get_Offset(0, c).Value2.ToString(),
                    DataType = typeof(object),
                    AutoIncrement = false,
                    ReadOnly = false,
                    Unique = false,
                });
            }

            int ii = 1;
            for (int r = 1; r < range.Rows.Count; r++)
            {
                rg = rg.get_Offset(1, 0);
                object h = rg.EntireRow.Hidden;
                if (!bool.Equals(h, true))
                {
                    DataRow row = dt.NewRow();
                    dt.Rows.Add(row);
                    for (int c = 0; c < range.Columns.Count; c++)
                    {
                        if (rg.get_Offset(0, c).Value2 != null)
                        {
                            row[c] = rg.get_Offset(0, c).Value2.ToString();
                        }
                    }

                    Range rg1 = rg.get_Resize(1, range.Columns.Count);
                    rangeCache.Add(name + "#" + ii++, rg1);
                    SetColor(rg1, 36);
                }
            }
            return dt;
        }


        #endregion

        #region Run test
        private void btnRun_Click(object sender, RibbonControlEventArgs e)
        {
            try
            {
                var type = ((RibbonButton)sender).Tag as Type;
                RunTest(type);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        DataSet ds;
        Dictionary<string, Range> RangeCache;
        Worksheet logSheet;
        int logIdx, npassed, nfailed, nerror;

        private void RunTest(Type type)
        {
            npassed = nfailed = nerror = 0;

            RangeCache = new Dictionary<string, Range>();

            ds = CreateDataSet();
            Feature feature = CreateFeature();

            if (feature.Scenarios.Count == 0)
            {
                MessageBox.Show("No scenarios found, please check if you are on correct worksheek.");
                return;
            }


            Worksheet ws = Globals.ThisAddIn.Application.ActiveWorkbook.ActiveSheet as Worksheet;

            logSheet = Globals.ThisAddIn.Application.ActiveWorkbook.Sheets.Add(Type.Missing,
                Globals.ThisAddIn.Application.ActiveWorkbook.ActiveSheet, 1, Type.Missing) as Worksheet;
            logSheet.Name = ws.Name + "-" + DateTime.Now.ToString("MMdd-hhmmss");
            logIdx = 0;

            ws.Activate();

            TestRunner runner = new TestRunner();

            runner.LoadSteps(Activator.CreateInstance(type));

            if (logSheet != null)
            {
                runner.OnStatusUpdated += new EventHandler<ExecutionMessage>(runner_OnStatusUpdated);
            }
            runner.Run(feature, ds);

            MessageBox.Show(string.Format("{0} passed, {1} failed, {2} error", npassed, nfailed, nerror));
        }
        #endregion

        #region screen feedback and logging
        void runner_OnStatusUpdated(object sender, ExecutionMessage e)
        {
            if (e.Status != ExecutionStatus.Info && e.Status != ExecutionStatus.Begin &&
                e.Tag is AcceptanceTest.Framework.Scenario)
            {

                string status = e.Status.ToString();
                if (status == "End") status = "Passed";

                SetCellValue(++logIdx, 1, status, logSheet);
                SetCellValue(logIdx, 2, e.Tag.ToString(), logSheet);
                if (e.Exception != null)
                {
                    SetCellValue(logIdx, 3, e.Exception.Message, logSheet);
                }

                switch (e.Status)
                {
                    case ExecutionStatus.End:
                        npassed++;
                        break;

                    case ExecutionStatus.Failed:
                        nfailed++;
                        break;

                    case ExecutionStatus.Error:
                        nerror++;
                        break;
                }
            }

            if (e.Status == ExecutionStatus.Begin &&
               e.Tag is string && e.Tag.ToString().Contains("#0"))
            {
                string tablename = e.Tag.ToString();
                tablename = tablename.Substring(0, tablename.IndexOf("#0"));
                SetCellValue(++logIdx, 1, "Status", logSheet);
                SetCellValue(logIdx, 2, "Scenario", logSheet);

                System.Data.DataTable dt = ds.Tables[tablename];
                for (int c = 0; c < dt.Columns.Count; c++)
                {
                    SetCellValue(logIdx, 3 + c, dt.Columns[c].ColumnName, logSheet);
                }
            }

            if (e.Status != ExecutionStatus.Info && e.Status != ExecutionStatus.Begin &&
                e.Tag is string && e.Tag.ToString().Contains("#"))
            {
                string table_key = e.Tag.ToString();
                if (RangeCache.ContainsKey(table_key))
                {
                    Range row = RangeCache[table_key];
                    Range rg = row.get_Resize(1, 1);

                    for (int c = 0; c < row.Columns.Count; c++)
                    {
                        if (rg.get_Offset(0, c).Value2 != null)
                        {
                            SetCellValue(logIdx, 3 + c, rg.get_Offset(0, c).Value2.ToString(), logSheet);
                        }
                    }
                    if (e.Exception != null)
                    {
                        SetCellValue(logIdx, 3 + row.Columns.Count, e.Exception.Message, logSheet);
                    }
                }
            }

            string key = e.Tag.ToString();

            if (RangeCache.ContainsKey(key))
            {
                var range = RangeCache[key];
                switch (e.Status)
                {
                    case ExecutionStatus.Begin:
                        SetColor(range, 38);
                        break;

                    case ExecutionStatus.End:
                        SetColor(range, 34);
                        break;

                    case ExecutionStatus.Failed:
                        SetColor(range, 22);
                        break;

                    case ExecutionStatus.Error:
                        SetColor(range, 3);
                        break;
                }
            }

        }

        int idx = 1;
        private Feature CreateFeature()
        {
            Worksheet worksheet = Globals.ThisAddIn.Application.ActiveWorkbook.ActiveSheet as Worksheet;
            Feature feature = new Feature
            {
                Name = worksheet.Name,
                Scenarios = new List<AcceptanceTest.Framework.Scenario>()
            };

            for (idx = 1; idx < 65536; idx++)
            {
                string c1 = GetCellValue(idx, 1);
                if (string.Compare(c1, "scenario", true) == 0)
                {
                    feature.Scenarios.Add(CreateScenario());
                }
                if (GetCellValue(idx + 1, 2).Length == 0) break;
            }

            return feature;
        }

        #endregion

        #region Get data from screen
        private AcceptanceTest.Framework.Scenario CreateScenario()
        {
            Worksheet worksheet = Globals.ThisAddIn.Application.ActiveWorkbook.ActiveSheet as Worksheet;
            string name = GetCellValue(idx, 2);

            Range rg = worksheet.Cells[idx, 1] as Range;
            rg = rg.get_Resize(1, 2);
            SetColor(rg, 36);
            RangeCache.Add(name, rg);

            var scenario = new AcceptanceTest.Framework.Scenario { Name = name };
            var list = new List<string>();

            while (true)
            {
                string step = GetCellValue(++idx, 2);
                if (step.Length > 0)
                {
                    list.Add(step);
                    Range range = worksheet.Cells[idx, 1] as Range;
                    range = range.get_Resize(1, 2);
                    SetColor(range, 36);
                    RangeCache.Add(string.Format("{0}.{1}", name, step), range);
                }
                else
                    break;
            }

            scenario.Steps = list.ToArray();
            return scenario;
        }

        private DataSet CreateDataSet()
        {
            Worksheet worksheet = Globals.ThisAddIn.Application.ActiveWorkbook.ActiveSheet as Worksheet;
            DataSet ds = new DataSet();
            foreach (Name namedRange in Globals.ThisAddIn.Application.ActiveWorkbook.Names)
            {
                try
                {
                    if (!namedRange.Value.Contains("!#REF!"))
                    {
                        string sheetName = ((Worksheet)namedRange.RefersToRange.Parent).Name;
                        string tableName = namedRange.Name;

                        string fullame = (string.Compare(worksheet.Name, sheetName, true) != 0) ?
                            sheetName + "." + tableName : tableName;

                        System.Data.DataTable dt = GetDataTableFromRange(namedRange.RefersToRange, fullame, RangeCache);
                        ds.Tables.Add(dt);

                    }
                }
                catch //ignore bad ranges
                {
                }
            }
            return ds;
        }
        #endregion

        #region Load Test

        string configFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "acceptance-test-excel-addin.libs");
        
        private void LoadTestLibraryAuto()
        {
            tests = new Dictionary<string, Type>();
            if (File.Exists(configFileName))
            {
                string[] assemblies = File.ReadAllLines(configFileName);
                foreach (string assembly in assemblies)
                {
                    LoadTestLibrary(assembly, false);
                }
            }
        }

        private void SaveTestLibraryAuto()
        {
            var contents = File.Exists(configFileName) ? File.ReadAllText(configFileName) : "";

            foreach (var test in tests.Values)
            {
                if (!contents.Contains(test.Assembly.CodeBase))
                {
                    contents += test.Assembly.CodeBase + "\r\n";
                }
            }

            if(!string.IsNullOrEmpty(contents)) File.WriteAllText(configFileName, contents);
        }

        private void btnLoad_Click(object sender, RibbonControlEventArgs e)
        {
            try
            {
                if (openFileDialog1.ShowDialog() == DialogResult.OK)
                {
                    LoadTestLibrary(openFileDialog1.FileName, true);
                    AddTestLibraryToUI(tests);
                    SaveTestLibraryAuto();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void LoadTestLibrary(string fileName, bool showMessage)
        {
            Assembly assembly = Assembly.LoadFrom(fileName);
            foreach (var type in assembly.GetTypes())
            {
                foreach (StepDefinitionAttribute attr in type.GetCustomAttributes(typeof(StepDefinitionAttribute), true))
                {
                    tests.Add(attr.Name, type);
                    if (showMessage)
                    {
                        MessageBox.Show("Loaded: " + attr.Name);
                    }
                }
            }
        }

        private void AddTestLibraryToUI(Dictionary<string, Type> tests)
        {
            menuRun.Items.Clear();
            foreach (string key in tests.Keys)
            {
                var button = new RibbonButton
                {
                    Label = key,
                    Tag = tests[key],
                };

                button.Click += new EventHandler<RibbonControlEventArgs>(btnRun_Click);
                menuRun.Items.Add(button);
            }
        }

        #endregion
    }
}
